package com.dataprocess.samples.vehicle.controller;


import com.dataprocess.samples.vehicle.model.CollisionList;
import com.dataprocess.samples.vehicle.model.ResultModel;
import com.dataprocess.samples.vehicle.model.VehicleRealtimeData;
import com.google.common.base.Strings;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import org.json.JSONObject;
import org.flywaydb.core.Flyway;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

@RestController
@RefreshScope
@RequestMapping("/api")
@CrossOrigin(origins = "*")
public class VehicleController {

    @Value("${config.type:ss}")
    private String configType;

    @Value("${postgres.datasourceurl:jdbc:postgresql://db:5432/postgres}")
    private String dataSourceUrl;

    @Value("${postgres.datasourceuser:postgres}")
    private String dataSourceUser;

    @Value("${postgres.datasourcepassword:postgres123}")
    private String dataSourcePassword;

    @Value("${redis.redisserver:redis}")
    private String redisServer;

    @Value("${redis.redisport:6379}")
    private int redisPort;

    static int warningCount = 0;

    @Autowired
    private Jedis jedis;


    @GetMapping("/config")
    public String config() {
        String str = Strings.lenientFormat("---------------------------config.type:%s \n", configType);
        str += Strings.lenientFormat("----------------postgres.datasourceurl:%s \n", dataSourceUrl);
        str += Strings.lenientFormat("---------------postgres.datasourceuser:%s \n", dataSourceUser);
        str += Strings.lenientFormat("-----------postgres.datasourcepassword:%s \n", dataSourcePassword);
        str += Strings.lenientFormat("---------------------redis.redisserver:%s \n", redisServer);
        str += Strings.lenientFormat("-----------------------redis.redisport:%s \n", redisPort);
        return str;
    }

    @PostMapping("/control")
    public ResultModel control(@RequestBody String controlInfo) {
        ResultModel resultModel = new ResultModel();
        try {
            jedis.publish("controls", controlInfo);
            resultModel.setErrCode(0);
            resultModel.setMsg("Control information sent to Redis channel 'controls': " + controlInfo);
        } catch (Exception e) {
            resultModel.setErrCode(1);
            resultModel.setMsg(e.toString());
        }
        return resultModel;
    }


    @GetMapping("/assignEmergencyInfo")
    public ResultModel assignEmergencyInfo(Integer id, String assignee) {
        ResultModel resultModel = new ResultModel();
        if (id == 0 || Strings.isNullOrEmpty(assignee)) {
            resultModel.setErrCode(1);
            resultModel.setMsg("id,assignee Parameter cannot be empty");
            return resultModel;
        }
        try (Connection dbConn = connectToDB(dataSourceUrl)) {
            PreparedStatement update = dbConn.prepareStatement(
                    "UPDATE collision SET state=?,assignee=?,\"time\"=CURRENT_TIMESTAMP WHERE id=?");
            update.setString(1, "Processing");
            update.setString(2, assignee);
            update.setInt(3, id);
            int res = update.executeUpdate();
            if (res > 0) {
                resultModel.setErrCode(0);
                resultModel.setMsg("update success");
            } else {
                resultModel.setErrCode(1);
                resultModel.setMsg("update Error");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return resultModel;
    }

    @GetMapping("/getMyEmergencyInfos")
    public List<CollisionList> getMyEmergencyInfos(String assignee) {

        String sql = "SELECT a.ID, a.vin, b.brand, b.mod, b.name, b.phone, a.locationinfo, a.collisioninfo, a.descriptioninfo, a.state, a.time FROM collision a "
                + " INNER JOIN vehicle b ON a.vin = b.vin WHERE a.assignee = ? ORDER BY a.time desc";

        List<CollisionList> resultList = new ArrayList<>();
        try (Connection dbConn = connectToDB(dataSourceUrl)) {
            PreparedStatement select = dbConn.prepareStatement(sql);
            select.setString(1, assignee);
            ResultSet resultSet = select.executeQuery();
            while (resultSet.next()) {
                CollisionList vehicleData = new CollisionList();
                vehicleData.id = resultSet.getInt("ID");
                vehicleData.vin = resultSet.getString("vin");
                vehicleData.brand = resultSet.getString("brand");
                vehicleData.mod = resultSet.getString("mod");
                vehicleData.name = resultSet.getString("name");
                vehicleData.phone = resultSet.getString("phone");
                vehicleData.locationinfo = resultSet.getString("locationinfo");
                vehicleData.collisioninfo = resultSet.getString("collisioninfo");
                vehicleData.descriptioninfo = resultSet.getString("descriptioninfo");
                vehicleData.state = resultSet.getString("state");
                vehicleData.time = resultSet.getDate("time");
                resultList.add(vehicleData);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return resultList;
    }


    @PostMapping("/vehiclerealtimedata")
    public void vehicleRealtimeData(@RequestBody VehicleRealtimeData vehicleRealtimeData) throws SQLException {
        try (Connection dbConn = connectToDB(dataSourceUrl)) {
            for (int i = 0; i < 1000; i++) {
                InsertCarInfo(dbConn, vehicleRealtimeData);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        }
    }

    private void InsertCarInfo(Connection dbConn, VehicleRealtimeData vehicleRealtimeData)
            throws SQLException {
        PreparedStatement insert = dbConn.prepareStatement(
                "INSERT INTO vehicle_realtime_data (vin, speed, location, mileage, engine_status, engine_temperature, driving_mode) VALUES (?, ?, ?, ?, ?, ?, ?)");
        insert.setString(1, vehicleRealtimeData.vin);
        insert.setString(2, vehicleRealtimeData.speed);
        insert.setString(3, vehicleRealtimeData.location);
        insert.setString(4, vehicleRealtimeData.mileage);
        insert.setString(5, vehicleRealtimeData.engine_status);
        insert.setString(6, vehicleRealtimeData.engine_temperature);
        insert.setString(7, vehicleRealtimeData.driving_mode);
        insert.executeUpdate();
        System.err.printf("Vehicle Realtime Data for '%s' inserted! \n", vehicleRealtimeData.vin);
    }


    @GetMapping("/getvehicledata")
    public List<VehicleRealtimeData> vehicleRealtimeData(@RequestParam String vin) throws SQLException {
        List<VehicleRealtimeData> resultList = new ArrayList<>();
        try (Connection dbConn = connectToDB(dataSourceUrl)) {
            PreparedStatement select = dbConn.prepareStatement(
                    "SELECT name, vin, speed, location, mileage, engine_status, engine_temperature, driving_mode, time FROM vehicle_realtime_data WHERE vin = ?");
            select.setString(1, vin);
            ResultSet resultSet = select.executeQuery();
            while (resultSet.next()) {
                VehicleRealtimeData vehicleData = new VehicleRealtimeData();
                vehicleData.vin = resultSet.getString("vin");
                vehicleData.speed = resultSet.getString("speed");
                vehicleData.location = resultSet.getString("location");
                vehicleData.mileage = resultSet.getString("mileage");
                vehicleData.engine_status = resultSet.getString("engine_status");
                vehicleData.engine_temperature = resultSet.getString("engine_temperature");
                vehicleData.driving_mode = resultSet.getString("driving_mode");
                vehicleData.time = resultSet.getString("time");
                resultList.add(vehicleData);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw e;
        }
        return resultList;
    }

    @PostConstruct
    public void init() {

        try {
            System.out.printf("---------------------------config.type:%s \n", configType);
            System.out.printf("----------------postgres.datasourceurl:%s \n", dataSourceUrl);
            System.out.printf("---------------postgres.datasourceuser:%s \n", dataSourceUser);
            System.out.printf("-----------postgres.datasourcepassword:%s \n", dataSourcePassword);
            System.out.printf("---------------------redis.redisserver:%s \n", redisServer);
            System.out.printf("-----------------------redis.redisport:%s \n", redisPort);

            System.out.printf("Connecting database...\n");
            Connection dbConn = connectToDB(dataSourceUrl);
            System.out.printf("Starting database migration...\n");
            dbMigration();
            System.out.printf("Database migration done!\n");
            System.out.printf("Connecting redis...\n");

            JedisPoolConfig poolConfig = new JedisPoolConfig();
            JedisPool jedisPool = new JedisPool(poolConfig, redisServer, redisPort, 0);
            Jedis subscriberJedis = jedisPool.getResource();
            CollisionMessageListener subscriber = new CollisionMessageListener();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.err.printf("Subscribing to redis channel. This thread will be blocked.\n");
                        subscriberJedis.subscribe(subscriber, "collisions");
                        System.err.printf("Subscription ended.");
                    } catch (Exception e) {
                        System.err.printf("Subscribing failed.", e);
                    }
                }
            }).start();

        } catch (SQLException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    // Listener class for handling incoming messages on the 'collisions' channel
    class CollisionMessageListener extends JedisPubSub {

        @Override
        public void onMessage(String channel, String message) {
            System.err.printf("Got a redis message on channel '%s': %s \n", channel, message);
            // Handle the collision message here (parse JSON, insert into the database, etc.)
            JSONObject collisionData = new JSONObject(message);
            String vin = collisionData.getString("vin");
            String locationinfo = collisionData.getString("locationinfo");
            String collisioninfo = collisionData.getString("collisioninfo");
            String descriptioninfo = collisionData.getString("descriptioninfo");

            System.err.printf("Starting to insert collision message for '%s' \n", locationinfo);

            try (Connection dbConn = connectToDB(dataSourceUrl)) {
                InsertCollision(dbConn, vin, locationinfo, collisioninfo, descriptioninfo);
                int wc = count();
                System.err.printf("processing count is '%s'.\n\n", wc);
            } catch (SQLException e) {
                e.printStackTrace();
            }


        }
    }


    public void dbMigration() {
        try {
            String rootPath = System.getProperty("user.dir");
            String sqlpath = rootPath + "/target/classes/db/migrations";
            String migrationsPath = "";
            if (isDirectoryExists(sqlpath)) {
                migrationsPath = "filesystem:" + rootPath + "/target/classes/db/migrations";
            } else {
                migrationsPath = "filesystem:" + rootPath + "/vehicle/target/classes/db/migrations";
            }
            System.out.printf("----------------migrationsPath:%s \n", migrationsPath);

            // Create Flyway instance
            Flyway flyway = Flyway.configure().dataSource(dataSourceUrl, dataSourceUser, dataSourcePassword)
                    .locations(migrationsPath).load();
            // Execute database migration
            flyway.migrate();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public Connection connectToDB(String url) throws SQLException {
        Connection conn = null;
        try {
            Class.forName("org.postgresql.Driver");
            while (conn == null) {
                try {
                    conn = DriverManager.getConnection(url, dataSourceUser, dataSourcePassword);
                } catch (SQLException e) {
                    System.err.println("Waiting for db");
                    sleep(1000);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.exit(1);
        }

        // System.err.println("Connected to db \n");
        return conn;
    }

    static void sleep(long duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            System.exit(1);
        }
    }

    public static void InsertCollision(Connection dbConn, String vin, String locationinfo, String collisioninfo, String descriptioninfo)
            throws SQLException {
        PreparedStatement insert = dbConn.prepareStatement(
                "INSERT INTO collision (vin, state, locationinfo, collisioninfo, descriptioninfo,assignee) VALUES (?, ?, ?, ?, ?,?)");
        insert.setString(1, vin);
        insert.setString(2, "Pending");
        insert.setString(3, locationinfo);
        insert.setString(4, collisioninfo);
        insert.setString(5, descriptioninfo);
        insert.setString(6, "user");
        insert.executeUpdate();
        System.err.printf("Collision message for '%s' inserted! \n", vin);

    }

    public static int count() {
        warningCount = warningCount + 1;
        return warningCount;
    }

    public static boolean isDirectoryExists(String directoryPath) {
        Path path = Paths.get(directoryPath);
        return Files.exists(path) && Files.isDirectory(path);
    }

}

